import {normalize} from "path";
import {existsSync, readFileSync} from "fs";
import {CodeGeneratorRequest, CodeGeneratorRequestSchema, FileDescriptorSetSchema, FileDescriptorSet} from "@bufbuild/protobuf/wkt";
import {create, fromBinary} from "@bufbuild/protobuf";


const descriptorSetPath = normalize('./descriptors.binpb');


export function getFileDescriptorSet(): FileDescriptorSet {
    if (!existsSync(descriptorSetPath)) {
        const reason = `Did not find '${descriptorSetPath}'. \n`
            + `The file has to be generated by a protobuf compiler and contain a serialized FileDescriptorSet of all .protos in packages/proto.`
        pending(reason);
        throw reason;
    }
    const bytes = readFileSync(descriptorSetPath);
    return fromBinary(FileDescriptorSetSchema, bytes) as FileDescriptorSet;
}


interface MakeCodeGeneratorRequestOptions {
    parameter?: string;
    includeFiles?: string[]; // defaults to all files
    fileToGenerate?: string[]; // defaults to includeFiles
}

export function getCodeGeneratorRequest(options: MakeCodeGeneratorRequestOptions): CodeGeneratorRequest {
    const fileDescriptorSet = getFileDescriptorSet();
    const allProtoFilenames = fileDescriptorSet.file.map(fileProto => fileProto.name);
    const includeProtoFilenames = options.includeFiles ?? allProtoFilenames;

    const request = create(CodeGeneratorRequestSchema) as CodeGeneratorRequest;
    request.parameter = options.parameter ?? '';
    request.fileToGenerate = options.fileToGenerate ?? includeProtoFilenames;
    request.protoFile = fileDescriptorSet.file.filter(f => includeProtoFilenames.includes(f.name));

    // check if demand is satisfied
    const missingFileToGenerate = request.fileToGenerate.filter(n => !request.protoFile.some(f => f.name === n));
    if (missingFileToGenerate.length > 0) {
        let msg = `You requested ${missingFileToGenerate.length} files to generate that are not available in ${descriptorSetPath}:\n`;
        msg += missingFileToGenerate.join('\n');
        msg += "\n\navailable files: "
        msg += allProtoFilenames.join('\n');
        throw msg;
    }
    const missingIncludes = (options.includeFiles ?? []).filter(n => !request.protoFile.some(f => f.name === n));
    if (missingIncludes.length > 0) {
        let msg = `You requested to include ${missingIncludes.length} files that are not available in ${descriptorSetPath}:\n`;
        msg += missingIncludes.join('\n');
        throw msg;
    }

    // make sure to clone so that our cached descriptors stay unchanged
    return request;
}
